#include "CentralRowSolver.h"
#include <iostream>
#include <omp.h>
#include <algorithm>
using namespace std;

CentralRowSolver::CentralRowSolver():data_ptr(0)
{

}

inline
void CentralRowSolver::update_result(Result & result, t_int x0, t_int y0, t_int x1, t_int y1, t_int sum)
{
    result.x0 = x0;
    result.y0 = y0;
    result.x1 = x1;
    result.y1 = y1;
    result.sum = sum;
}


inline
void CentralRowSolver::initialize_accumulators( Result & best_result, Result & upper_result, Result & lower_result,
                                               vector<int> & accumulator_up, vector<int> & accumulator_down,
                                               int upper_y, int local_x1, int top_y, int down_y)
{
    const Mat & mat = *mat_ptr;
    const vector <int> & col = mat[local_x1];
    accumulator_up[0] = col[upper_y];
    accumulator_down[0] = col[upper_y + 1];
    //cout << "Accumulator up = " << accumulator_up[0] << " and accumulator down  = " << accumulator_down[0] << endl;
    int i = 1;
    //initializing upper structures
    for (int local_y_up = upper_y - 1;local_y_up >= top_y ; local_y_up--)
    {
        accumulator_up[i] = accumulator_up[i-1] + col[local_y_up];
        i++;
    }
    //initializing lower structures
    i = 1;
    for (int local_y_down = upper_y + 2 ; local_y_down <= down_y; local_y_down++)
    {
        accumulator_down[i] = accumulator_down[i-1] + col[local_y_down];
        i++;
    }

    //testing against actual best result
    int max_up, dist_up_from_center, max_down, dist_down_from_center;
	find_max_and_index(accumulator_up, max_up, dist_up_from_center);
	find_max_and_index(accumulator_down, max_down, dist_down_from_center);
    int total_sum = max_up + max_down;
    if (total_sum > best_result.sum)
    {
        update_result(best_result, local_x1, upper_y - dist_up_from_center,
                           local_x1, upper_y + 1 + dist_down_from_center, total_sum);
    }
}

void CentralRowSolver::find_max_and_index(const vector <int > & accumulator, int & maxi,
                                             int & dist_from_center)
{
    const vector<int>::const_iterator max_elem = max_element(accumulator.begin(), accumulator.end());
    maxi = *max_elem;
    dist_from_center = distance(accumulator.begin(), max_elem);
}

void CentralRowSolver::solve_MCS(const Data * original_data,  Result & result, const s_rect & rect, int central_row)
{
    this->data_ptr = original_data;
    this->mat_ptr = &(original_data->getMatTranspose());
    const Mat & mat = *mat_ptr;
    //#warning "Test what happens when trying to solve on a single row"
/*
	cout << "Central row : (" << rect.x0 << "," << rect.y0 << ") to (";
	cout << rect.x1 << "," 	<< rect.y1 << ") with central row = ";
    cout << central_row << " and thread = " << omp_get_thread_num() << endl;
*/
	int nb_rows_up = central_row - rect.y0 + 1;
	int nb_rows_down = rect.y1 - central_row;
	#pragma omp parallel
	{
	    Result best_result, upper_result, lower_result;
	vector<int> accumulator_up(nb_rows_up);
	vector<int> accumulator_down(nb_rows_down);
	int upper_y = rect.y0 + nb_rows_up - 1;

	update_result(best_result, rect.x0, upper_y, rect.x0, upper_y+1,
                    mat[rect.x0][upper_y] + mat[rect.x0][upper_y +1]);
#pragma omp  for schedule(static,10)
	for (int local_x1 = rect.x0 ; local_x1 <= rect.x1 ; local_x1++)
	{

	    initialize_accumulators( best_result, upper_result, lower_result,
                             accumulator_up, accumulator_down,
                             upper_y,local_x1, rect.y0, rect.y1);
		//cout << "Trying for " << local_x1 << " until " << rect.x1 << endl;
		for (int local_x2 = local_x1 + 1 ; local_x2 <= rect.x1 ; local_x2++)
		{
		    //initialise best upper result with X1, upper_y, x2, upper_y

			int local_accumulator_up = 0;
           int i = 0;
		   for (int local_y_up = upper_y  ; local_y_up >= rect.y0 ; local_y_up-- )
		   {
				local_accumulator_up += mat[local_x2][local_y_up];
				accumulator_up[i] += local_accumulator_up;
				i++;
			}
           i = 0;
           //initialise best lower result with x1, upper_y+1, x2, upper_y+1
            int local_accumulator_down = 0;
		   for (int local_y_down = upper_y + 1 ; local_y_down <= rect.y1 ; local_y_down++ )
		   {
				local_accumulator_down += mat[local_x2][local_y_down];
				accumulator_down[i] += local_accumulator_down;
				i++;
			}
int max_up, dist_up_from_center, max_down, dist_down_from_center;
			find_max_and_index(accumulator_up, max_up, dist_up_from_center);
			find_max_and_index(accumulator_down, max_down, dist_down_from_center);
			int total_sum = max_up + max_down;
			if (total_sum > best_result.sum)
			{
			    //cout << "new best result found" << endl;
			    //best_result.update(left_result.x0, left_result.y0, right_result.x1, right_result.y1, total_sum);
			    best_result.x0 = local_x1;
			    best_result.y0 = upper_y - dist_up_from_center;
			    best_result.x1 = local_x2;
			    best_result.y1 = upper_y + 1 + dist_down_from_center;
			    best_result.sum = total_sum;
			    //best_result.display();
			}
		}
	}
	#pragma omp critical
	{
        if (best_result > result)
            result = best_result;
	}
	}//pragma omp parallel

}
